package simpleftp.server;

import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.Void;
import java.util.concurrent.Callable;

import javax.swing.tree.DefaultMutableTreeNode;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import simpleftp.client.control.Command;

/**
 * ServeurFTP is a simple package that implements a Java FTP server. With
 * ServeurFTP, you can connect to an FTP client and download multiple files. 
 */
public class FTPServer implements Callable<Void> {
	private static boolean DEBUG = false;
	private String response, user;
	private int validations;
	private File f;
	private StringBuilder sb;
	private int portnum1, portnum2;
	private Properties p;
	private ServerSocket dl;
	private Socket dl2;
	private FileInputStream fis;
	private Socket socket = null;
	private BufferedReader reader = null;
	private BufferedWriter writer = null;
	// TO BE COMPLETED - 1 LINE - DONE
	private ObjectOutputStream sortie = null;
	
	public FTPServer(Socket s) {
		socket = s;
		validations = 0;
		try {
			p = new Properties();
			fis = new FileInputStream("users.properties");
			p.load(fis);
			try {
				reader = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));
				writer = new BufferedWriter(new OutputStreamWriter(
						socket.getOutputStream()));
				// TO BE COMPLETED - 1 LINE - DONE
				sortie = new ObjectOutputStream(socket.getOutputStream());

				sendLine("220 ");
				validations++;
				f = new File(System.getProperty("user.dir"));	
				System.out.println("FTP Server : new client from " + socket.getInetAddress().getHostAddress());
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		} catch (IOException ioex) {
			System.out.println("Error: Properties File didn't load");
			try {
				sendLine("error ");
			} catch (IOException e) {
				e.printStackTrace();
			}
			ioex.printStackTrace();
		}
	}

	
	public Void call() {
		try {
			response = readLine();
			if (response.startsWith("USER")) {
				if (p.containsKey(response.substring(5))) {
					user = response.substring(5);
					System.out.println("Server knows user " + response);
					sendLine("331 ");
					validations++;
				} else {
					System.out.println("Server does no know user " + response);
					sendLine("error ");
				}
			}
			response = readLine();
			if (response.startsWith("PASS")) {
				if (p.get((String) user).equals(response.substring(5))) {
					System.out.println("Server validates pass " + response);
					sendLine("230 ");
					validations++;
				} else {
					sendLine("error ");
				}
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			try {
				sendLine("error ");
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

		while (validations == 3) {
			try {
				response = readLine();

				switch (Command.valueOf(response.split(" ")[0])) {

				case PWD:
					sendLine("257 " + System.getProperty("user.dir"));
					break;

				case LIST:
					sb = new StringBuilder("200 ");
					String[] fileNames = f.list();
					for (String fileName : fileNames) {
						sb = sb.append(fileName + "|");
					}
					sendLine(sb.toString());
					// TO BE COMPLETED - 1 LINE - DONE
					sendObject(f);
					break;

				case CWD:
					System.out.println("CWD");
					System.setProperty("user.dir", response.substring(4));
					
					System.out.println("CWD new current directory:"
							+ System.getProperty("user.dir").toString());
					f = new File(System.getProperty("user.dir"));
					sendLine("250 ");
					break;

				case PASV:
					pasv();
					break;

				case STOR:
					stor();
					break;
					
				case RETR:
					retr();
					break;

				case QUIT:
					socket.close();
					System.out.println("QUIT :Goodbye");
					validations = 0;
					break;

				default:
					System.out.println("Unknown Command.");
					sendLine("error");
					break;

				}
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {

					e1.printStackTrace();
				}
				e.printStackTrace();
			}

		}
		Void Void = null;
		return Void;
	}
	
	// TO BE COMPLETED - SEND SERIALIZABLE OBJECT OVER THE NETWORK - 5 LINES 
	//METHOD TREE
	public void sendObject(File f) //throws IOException
	{
		if(socket==null)
		{
			try {
				throw new IOException("FTPServer is not connected");
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		try {
			
			/***** Previous Method with File ******/
			//sortie.writeObject(f);
			/*************************************/
			
			
			/***** JSon Serialization Method ******/
			//Create the jsonTree
			JSONObject jsonTree=new JSONObject();
			//Populate it
			jsonPopulate(f,jsonTree);
			//Give the name of the file
			jsonTree.put("name", f.getName());
			
			//Print the json file
			/*StringWriter out = new StringWriter();
			try {
				jsonTree.writeJSONString(out);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			String jsonText = out.toString();
			System.out.print("jsonfinal:"+jsonText);*/
			
			//Send the tree to the client
			sortie.writeObject(jsonTree);
			
			
			/*************************************/
			
			
			/*******Method with DefaultMutableTreeNode*******/
			
			//Have to change in the SimpleFTP the return type of fileTree from JSONObject to DefaultMutableTreeNode
			///Create a new node with the file name
			/*DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(f.getName());
			//Populate the tree from the root
			populate(userRoot,f);
			//Send the tree to the client
			sortie.writeObject(userRoot);*/
			
			/********************************************/

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//Serialize the tree with jSon
	public void jsonPopulate(File f, JSONObject jsonTree)
	{
		//Create a JsonArray that contains the children
		JSONArray children=new JSONArray();
		
		//For every file in the tree
		for(File f1 : f.listFiles())
		{
			//Create a jsonObject with the name of the file
			JSONObject child=new JSONObject();
			child.put("name",f1.getName());
			
			//If it is not a directory set the type of file
			if(f1.isDirectory()==false) 
			{
				child.put("type","file");	
			}
			else  //Else if it is a directory repopulate from it
			{
				child.put("type","directory");
				jsonPopulate(f1,child);
			}
			
			//Add object to the list
			children.add(child);
		}
		//Add the list to the parent and set his name
		jsonTree.put("children",children);
		jsonTree.put("name",f.getName());
	}
	
	//Populate with the defaultmutabletreeNode method
	private void populate(DefaultMutableTreeNode userRoot, File root) 
	{
		//Get all the files in the root
		File [] rootfiles = root.listFiles();
		
		//For every file in the root
		for(File f : rootfiles)
		{
			//Create a new node and add it to the tree wether it is a directory or not
			DefaultMutableTreeNode child = new DefaultMutableTreeNode(f.getName());
			userRoot.add(child);
			//If the file is a directory redo the populateTree for this directory as the root
			if(f.isDirectory()==true) populate(child, f);		
		}
	}
	
	
	
	/**
     * Sends a raw command to the FTP server.
     */

	private void sendLine(String line) throws IOException {
		
		if (socket == null) {
			throw new IOException("FTPServer is not connected.");
		}
		try {
			writer.write(line + "\r\n");
			writer.flush();
			if (DEBUG) {
				System.out.println("> " + line);
			}
		} catch (IOException e) {
			socket = null;
			throw e;
		}
	}

	private String readLine() throws IOException {
		String line = reader.readLine();
		if (DEBUG) {
			System.out.println("< " + line);
		}
		return line;
	}
	
	//To receive a file
	public boolean stor() {
		boolean result=false;
		try {
			
			dl2 = dl.accept();
			
			sendLine("150 ");
			
			OutputStream os = new FileOutputStream(new File(System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			InputStream is = dl2.getInputStream();
			
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("STOR " + response.split(" ")[1]
					+ " : File received");
			result=true;
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
		return result;
	}
	
	//to send a file
	public boolean retr() {
		boolean result=false;
		try {
			
			String temp = response.substring(5);
			String part[];
			part = temp.split(";");
			String name = part[0];
			String path = part[1];
			
			//System.out.println("NAME = "+name);
			//System.out.println("PATH = "+path);
			
			dl2 = dl.accept();
			
			sendLine("150 ");
			
			//System.out.println(response.substring(5));
			
			//*

			OutputStream os = new FileOutputStream(new File(System.getProperty("user.dir")+ System.getProperty("file.separator")+ "Downloads"+System.getProperty("file.separator")+ name));
			InputStream is = dl2.getInputStream();
			
			
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) {
				System.out.println("Reading");
				os.write(buffer, 0, bytesRead);
			}
			
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("RETR " + response.split(" ")[1]
					+ " : File sent");
			result=true;
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
		return result;
	}

	private void pasv() {
		try {
			dl = new ServerSocket(0);
			portnum1 = dl.getLocalPort() / 256;
			portnum2 = dl.getLocalPort() % 256;
			sendLine("227 ("
					+ InetAddress.getLocalHost().getHostAddress()
							.replace('.', ',') + "," + portnum1 + ","
					+ portnum2 + ")");
			System.out.println("PASV success");
		} catch (IOException e) {
			try {
				sendLine("error ");
				System.out.println("pasv error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	public static void setDebug(boolean b) {
		DEBUG = b;
	}
}
